#include <linux/errno.h>
#include <linux/sched.h>

static int generate(unsigned long sig, struct task_struct *p) {
    unsigned long mask = 1 << (sig - 1);
    struct sigaction *sa = sig + p->sigaction - 1;
    /* don't bother with ignored signals (but SIGCHLD is special) */
    if (sa->sa_handler == SIG_IGN && sig != SIGCHLD)
        return 0;
    /* some signals are ignored by default.. (but SIGCONT already did its deed) */
    if ((sa->sa_handler == SIG_DFL) &&
        (sig == SIGCONT || sig == SIGCHLD || sig == SIGWINCH))
        return 0;
    p->signal |= mask;
    return 1;
}

int send_sig(unsigned long sig, struct task_struct *p, int priv) {
    if (!p || sig > 32)
        return -EINVAL;
    if (!priv && ((sig != SIGCONT) || (current->session != p->session)))
        return -EPERM;
    if (!sig)
        return 0;
    if ((sig == SIGKILL) || (sig == SIGCONT)) {
        if (p->state == TASK_STOPPED)
            p->state = TASK_RUNNING;
        p->exit_code = 0;
        p->signal &= ~((1 << (SIGSTOP - 1)) | (1 << (SIGTSTP - 1)) |
                       (1 << (SIGTTIN - 1)) | (1 << (SIGTTOU - 1)));
    }
    /* Depends on order SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU */
    if ((sig >= SIGSTOP) && (sig <= SIGTTOU))
        p->signal &= ~(1 << (SIGCONT - 1));
    /* Actually generate the signal */
    generate(sig, p);
    return 0;
}

int kill_pg(int pgrp, int sig, int priv) {
    struct task_struct *p;
    int err, retval = -ESRCH;
    int found = 0;

    if (sig < 0 || sig > 32 || pgrp <= 0)
        return -EINVAL;
    for_each_task(p) {
        if (p->pgrp == pgrp) {
            if ((err = send_sig(sig, p, priv)) != 0)
                retval = err;
            else
                found++;
        }
    }
    return (found ? 0 : retval);
}

int is_orphaned_pgrp(int pgrp) {
    struct task_struct *p;

    for_each_task(p) {
        if ((p->pgrp != pgrp) ||
            (p->state == TASK_ZOMBIE) ||
            (p->p_pptr->pid == 1))
            continue;
        if ((p->p_pptr->pgrp != pgrp) &&
            (p->p_pptr->session == p->session))
            return 0;
    }
    return (1); /* (sighing) "Often!" */
}

static int has_stopped_jobs(int pgrp) {
    struct task_struct *p;

    for_each_task(p) {
        if (p->pgrp != pgrp)
            continue;
        if (p->state == TASK_STOPPED)
            return (1);
    }
    return (0);
}

void notify_parent(struct task_struct *tsk) {
    if (tsk->p_pptr == task[1])
        tsk->exit_signal = SIGCHLD;
    send_sig(tsk->exit_signal, tsk->p_pptr, 1);
}

static void forget_original_parent(struct task_struct *father) {
    struct task_struct *p;

    for_each_task(p) {
        if (p->p_opptr == father) {
            if (task[1])
                p->p_opptr = task[1];
            else
                p->p_opptr = task[0];
        }
    }
}

void do_exit(long code) {
    struct task_struct *p;

fake_volatile:
    free_page_tables(current);
    forget_original_parent(current);

    current->state = TASK_ZOMBIE;
    current->exit_code = code;

    if ((current->p_pptr->pgrp != current->pgrp) &&
        (current->p_pptr->session == current->session) &&
        is_orphaned_pgrp(current->pgrp) &&
        has_stopped_jobs(current->pgrp)) {
        kill_pg(current->pgrp, SIGHUP, 1);
        kill_pg(current->pgrp, SIGCONT, 1);
    }
    notify_parent(current);

    p = current->p_cptr;
    while (p != NULL) {
        current->p_cptr = p->p_osptr;
        p->p_ysptr = NULL;
        if (task[1] && task[1] != current)
            p->p_pptr = task[1];
        else
            p->p_pptr = task[0];
        p->p_osptr = p->p_pptr->p_cptr;
        p->p_osptr->p_ysptr = p;
        p->p_pptr->p_cptr = p;
        p = current->p_cptr;
        if (p->state == TASK_ZOMBIE)
            notify_parent(p);
        if ((p->pgrp != current->pgrp) &&
            (p->session == current->session) &&
            is_orphaned_pgrp(p->pgrp) &&
            has_stopped_jobs(p->pgrp)) {
            kill_pg(p->pgrp, SIGHUP, 1);
            kill_pg(p->pgrp, SIGCONT, 1);
        }
    }

    schedule();

    goto fake_volatile;
}

int sys_exit(int error_code) {
    do_exit((error_code & 0xff) << 8);
}
